#ifndef THREAD_CACHED_ARRAY_H
#define THREAD_CACHED_ARRAY_H

#include <windows.h>

// This class can be used to sequentially read hude amount
// of data into a limited buffer. All read operations are done
// in the background thread.
template<class T, unsigned int N>
class ThreadCachedArray
{
public:

	ThreadCachedArray(T *buffer);
	~ThreadCachedArray();

	bool getData(unsigned int id, const T* *pData);

	bool startCaching(const std::string& fileName);
	void stopCaching();

protected:

	static DWORD WINAPI ReadingThread(LPVOID lpParam);

	T *m_buffer;
	unsigned int m_first;
	unsigned int m_last;
	unsigned int m_firstID;
	unsigned int m_fileOffset;

	std::string m_fileName;

	HANDLE m_thread;

	CRITICAL_SECTION m_crSection;

private:

	void clearVariables();

	static unsigned int cycleToLinearID(unsigned int head, unsigned int id);
	static unsigned int linearToCycleID(unsigned int head, unsigned int id);

	virtual void readToCache(T* data, unsigned int hugeFileID, HANDLE file) = 0;

};

template<class T, unsigned int N>
ThreadCachedArray<T, N>::ThreadCachedArray(T *buffer)
: m_buffer(buffer)
{
	_ASSERT(NULL != buffer);
	clearVariables();
	InitializeCriticalSection(&m_crSection);
}

template<class T, unsigned int N>
ThreadCachedArray<T, N>::~ThreadCachedArray()
{
	DeleteCriticalSection(&m_crSection);
}

template<class T, unsigned int N>
bool ThreadCachedArray<T, N>::getData(unsigned int id, const T* *pData)
{
	_ASSERT(NULL != pData);

	EnterCriticalSection(&m_crSection);

	unsigned int linLast = cycleToLinearID(m_first, m_last);
	unsigned int lastID = linLast + m_firstID;

	// Check the range
	if (id < m_firstID ||
		id >= lastID)
	{
		// Current cache miss. Have to restart caching ...
		m_first = m_last = 0;
		m_firstID = id;
		*pData = NULL;
		LeaveCriticalSection(&m_crSection);
		return false;
	}

	// Ok, we've got cache hit!
	unsigned int delta = id - m_firstID;
	m_first = (m_first + delta) % N;
	m_firstID = id;

	// Let worker thread to fill space we've just freed
	LeaveCriticalSection(&m_crSection);

	*pData = &m_buffer[m_first];

	return true;
}

template<class T, unsigned int N>
bool ThreadCachedArray<T, N>::startCaching(const std::string& fileName)
{
	_ASSERT(m_thread == NULL); // Should close first
	// Sanity check
	FILE* file = fopen(fileName.c_str(), "rb");
	if (NULL == file)
		return false;
	fclose(file);
	m_fileName = fileName;
	m_thread = CreateThread( 
		NULL,                   // default security attributes
		0,                      // use default stack size  
		ReadingThread,       // thread function name
		this,          // argument to thread function 
		0,                      // use default creation flags 
		NULL);   // returns the thread identifier 
	return true;
}

template<class T, unsigned int N>
void ThreadCachedArray<T, N>::stopCaching()
{
	// Hackish way, but works :)
	TerminateThread(m_thread, 0);
	clearVariables();
}

template<class T, unsigned int N>
DWORD WINAPI ThreadCachedArray<T, N>::ReadingThread(LPVOID lpParam)
{
	ThreadCachedArray<T, N> *ths = reinterpret_cast<ThreadCachedArray<T, N>*>(lpParam);

	//FILE* file = fopen(ths->m_fileName.c_str(), "rb");
	//if (NULL == file)
	//	return 1;
	HANDLE file = CreateFile(
		ths->m_fileName.c_str(),
		GENERIC_READ,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (NULL == file)
		return 1;

	while (1)
	{
		unsigned int first, last, firstID;
		EnterCriticalSection(&ths->m_crSection);
		first = ths->m_first;
		last = ths->m_last;
		firstID = ths->m_firstID;
		LeaveCriticalSection(&ths->m_crSection);

		// Not let's read
		unsigned int linLast;
		linLast = cycleToLinearID(first, last);
		if (linLast < N)
		{
			unsigned int fileOffst = ths->m_fileOffset + sizeof(T) * (ths->m_firstID + linLast);
//			fseek(file, fileOffst, SEEK_SET);
//			fread(&ths->m_buffer[last], sizeof(T), 1, file);
//			ths->m_readMethod(&ths->m_buffer[last], file);
			ths->readToCache(&ths->m_buffer[last], ths->m_firstID + linLast, file);
		}
		EnterCriticalSection(&ths->m_crSection);
		ths->m_last = (ths->m_last + 1) % N;
		LeaveCriticalSection(&ths->m_crSection);
	}

//	fclose(file);
	CloseHandle(file);

	return 0;
}

template<class T, unsigned int N>
void ThreadCachedArray<T, N>::clearVariables()
{
	m_first = 0;
	m_last = 0;
	m_firstID = 0;
	m_fileOffset = 0;
	m_thread = NULL;
	m_fileName.clear();
}

template<class T, unsigned int N>
unsigned int ThreadCachedArray<T, N>::cycleToLinearID(unsigned int head, unsigned int id)
{
	if (id > head)
		return (id - head);
	else
		return (id + N - head) % N;
}

template<class T, unsigned int N>
unsigned int ThreadCachedArray<T, N>::linearToCycleID(unsigned int head, unsigned int id)
{
	return ((head + id) % N);
}

#endif // #ifndef THREAD_CACHED_ARRAY_H